/*
 *    AdaptiveFrequentItemsetLearner.java
 *    Copyright (C) 2010 University of Waikato, Hamilton, New Zealand
 *    @author Albert Bifet (abifet@cs.waikato.ac.nz)
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package moa.learners;

import java.util.ArrayList; 
import java.util.Collections; 
import java.util.HashMap; 
import java.util.List;
import java.util.StringTokenizer;

import moa.MOAObject;
import moa.classifiers.ADWIN;
import moa.core.DoubleVector;
import moa.core.Measurement;
import moa.core.StringUtils;
import moa.options.FloatOption;
import moa.options.IntOption;
import weka.core.Instance;

public class AdaptiveFrequentItemsetLearner extends AbstractLearner {

	private static final long serialVersionUID = 1L;
	
	/*public IntOption windowSizeOption = new IntOption(
			"windowSize", 'w',
			"Size of the sliding window.", 5000);*/ //**
			
	public IntOption batchSizeOption = new IntOption(
			"batchSize", 'b',
			"Size of a batch of examples.", 1000);
	
	public FloatOption minSupportOption = new FloatOption(
			"minSupport", 's',
			"Minimum support.", 0.1, 0, 1);
			
	@SuppressWarnings("hiding")
	public static final String classifierPurposeString = "Simple Incremental Frequent Itemset Learner.";

	protected ArrayList<HashMap<List<Integer>, Integer>> window;
	protected HashMap<List<Integer>, Integer> frequentItemsets;
	protected ArrayList<List<Integer>> batchItemsets;
	protected int currentWindowBatch;
	
	protected ADWIN SizeWindowEstimator; //**
  
	@Override
	public void resetLearningImpl() {
	    // inits a window of batches W
        this.window = new ArrayList<HashMap<List<Integer>, Integer>>();

        // inits a list of frequent itemsets storing sequences and supports
    	this.frequentItemsets = new HashMap<List<Integer>, Integer>();
    	// inits batch 
		this.batchItemsets = new ArrayList<List<Integer>>();
		this.currentWindowBatch = 0;
		this.SizeWindowEstimator = new ADWIN(); //**

	}
	
	public List<Integer> itemsetInstance(Instance inst){
		List<Integer> itemset = new ArrayList<Integer>();
		String stringInstance = inst.toString();
		stringInstance = stringInstance.substring(1, stringInstance.length()-1);
		StringTokenizer st = new StringTokenizer(stringInstance);
		while (st.hasMoreTokens()) {
			itemset.add(Integer.parseInt(st.nextToken()));	
		}
		return itemset;
	}
	
	public String toString(List<Integer> itemSet){
			StringBuffer r = new StringBuffer ();
			for(int attribute : itemSet){
				r.append(attribute);
				r.append(' ');
			}
			return r.toString();
		}

	protected HashMap<List<Integer>, Integer> BatchMiner(ArrayList<List<Integer>> batchItemsets){
		// TO DO: Replace by a frequent itemset learner
		HashMap<List<Integer>, Integer> batchFrequentItemsets = new HashMap<List<Integer>, Integer>();
		for ( List<Integer> is : batchItemsets){
			//Compute subsets of is and update support
			for ( int i = 0; i< Math.pow(2, is.size()) -1; i++) {
				 List<Integer> subset = new ArrayList<Integer>();
				 int num = 1;
				 // Each subset corresponds to a binary number
				 for( int j = 0; j< is.size();j++) {
					 if ((i & num) != num) //is zero 
					 	subset.add(is.get(j));
					 num = num << 1;
				 }
				 //Update support
				 int oldSupport = batchFrequentItemsets.get(subset) != null ? batchFrequentItemsets.get(subset) :0 ;
				 batchFrequentItemsets.put(subset, oldSupport  + 1);
			}
		}
		double minSupport =  this.minSupportOption.getValue() * this.batchSizeOption.getValue();
		deleteInfrequentItemsets(batchFrequentItemsets, minSupport);
		System.out.println("Batch size: "+batchFrequentItemsets.size());
		return batchFrequentItemsets;
	}

    protected void deleteInfrequentItemsets(HashMap<List<Integer>, Integer> batchFrequentItemsets, double minSupport) {
		ArrayList<List<Integer>> itemsetsDelete = new ArrayList<List<Integer>>();
		for ( List<Integer> is : batchFrequentItemsets.keySet()){
			// remove infrequent patterns
			 if (batchFrequentItemsets.get(is) < minSupport){
				 itemsetsDelete.add(is);
			 } 
		 }
		 for ( List<Integer> is :  itemsetsDelete){
			 batchFrequentItemsets.remove(is);
		 }
	 }

	@Override
	public void trainOnInstanceImpl(Instance inst) {
		// every new instance is stored in a batch 
		List<Integer> itemset = itemsetInstance(inst);
		this.batchItemsets.add(itemset);
		// when the current batch is full
		if ( this.batchItemsets.size() == this.batchSizeOption.getValue()) {
			// compute the frequent itemsets using a batch method and store them in the window
			HashMap<List<Integer>, Integer> batchfrequentItemsets = BatchMiner(this.batchItemsets); 
			this.window.add(batchfrequentItemsets);
			
    		// add these new frequent itemsets, updating support counts
    		for ( List<Integer> is : batchfrequentItemsets.keySet()){
				 if (this.frequentItemsets.get(is) != null) {
				 	this.frequentItemsets.put(is, this.frequentItemsets.get(is) + batchfrequentItemsets.get(is));
				 } else {
					this.frequentItemsets.put(is, batchfrequentItemsets.get(is)); 
				 }
			}
			this.SizeWindowEstimator.setInput(this.frequentItemsets.size()); //**
			
       		// if the window is full remove data from oldest batch
       		while (this.window.size() > this.SizeWindowEstimator.getWidth()){ //**
				//delete the old frequent patterns from the oldest batch in W, updating support counts
				for ( List<Integer> is : this.window.get(0).keySet()){
					if (this.frequentItemsets.get(is)!= null){
						this.frequentItemsets.put(is, this.frequentItemsets.get(is) - this.window.get(0).get(is));
					} else {
						this.frequentItemsets.put(is, 0);
					}
				}
				this.window.remove(0);
			}
			System.out.println(this.SizeWindowEstimator.getWidth());
			double minSupport = this.minSupportOption.getValue() * this.window.size() * this.batchSizeOption.getValue();
			deleteInfrequentItemsets(this.frequentItemsets, minSupport);
   			// start a new batch	
			this.batchItemsets = new ArrayList<List<Integer>>();
			this.currentWindowBatch++;
		}
	}

	@Override
	protected Measurement[] getModelMeasurementsImpl() {
		return null;
	}

	@Override
	public void getModelDescription(StringBuilder out, int indent) {
		// Output frequent itemsets
		StringUtils.appendIndented(out, indent, "Number of frequent itemsets: ");
		out.append(this.frequentItemsets.size());
		StringUtils.appendNewline(out);
		for ( List<Integer> is : this.frequentItemsets.keySet()){
			StringUtils.appendIndented(out, indent, "Support of <");
			for ( Integer i : is){
				out.append(" ");
				out.append(i);
			}
			out.append(" >: ");
			out.append(this.frequentItemsets.get(is));
			StringUtils.appendNewline(out);
		}
	}

	@Override
	public MOAObject getModel() {
		return null;
	}

	public boolean isRandomizable() {
		return false;
	}

}
